package jnpf.filter;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import jnpf.base.ActionResultCode;
import jnpf.base.UserInfo;
import jnpf.constant.MsgCode;
import jnpf.consts.AuthConsts;
import jnpf.engine.util.SpringContextHolder;
import jnpf.exception.LoginException;
import jnpf.exception.TenantDatabaseException;
import jnpf.model.BaseSystemInfo;
import jnpf.properties.GatewayWhite;
import jnpf.service.LoginService;
import jnpf.service.impl.LoginServiceImpl;
import jnpf.util.IpUtil;
import jnpf.util.TenantProvider;
import jnpf.util.UserProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static jnpf.consts.AuthConsts.USERDETAIL_USER_ID;



@Slf4j
@Configuration
public class AuthFilter {


    private static final String ALL = "*";
    private static final String MAX_AGE = "18000L";



    @Value("${rsa.privateKey}")
    private String privateKey;

    @Value("${open.list}")
    private String openList;
    // 注册 Sa-Token全局过滤器

    private LoginService getLoginService() {
        return SpringContextHolder.getBean(LoginServiceImpl.class);
    }

    @Bean
    public SaServletFilter getSaReactorFilter(GatewayWhite gatewayWhite) {
        return new SaServletFilter()
                // 拦截地址
                .addInclude("/**")
                .setExcludeList(gatewayWhite.excludeUrl)
                // 鉴权方法：每次访问进入
                .setAuth(obj -> {
                    if(log.isInfoEnabled()){
                        log.info("请求路径: {}", SaHolder.getRequest().getRequestPath());
                    }
                    //拦截路径
                    SaRouter.match(gatewayWhite.blockUrl).match(o -> {
                        //禁止访问URL 排除白名单
                        String ip = getIpAddr();
                        for (String o1 : gatewayWhite.whiteIp) {
                            if(ip.startsWith(o1)){
                                return false;
                            }
                        }
                        log.info("非白名单IP访问限制接口：{}, {}", SaHolder.getRequest().getRequestPath(), ip);
                        return true;
                    }).back(MsgCode.AD101.get());

                    //测试不验证 鉴权服务重启测试模式不清除Token就够了
                    //SaRouter.match((r)->"true".equals(configValueUtil.getTestVersion())).stop();
                    //白名单不拦截
                    SaRouter.match(gatewayWhite.whiteUrl).stop();
                    //内部请求不拦截
                    SaRouter.match(t->{
                        String innerToken = SaHolder.getRequest().getHeader(AuthConsts.INNER_TOKEN_KEY);
                        return UserProvider.isValidInnerToken(innerToken);
                    }).stop();
                    // 登录校验 -- 校验多租户管理模块TOKEN
                    //SaRouter.match("/api/tenant/**", r -> {
                    //    SaManager.getStpLogic(AuthConsts.ACCOUNT_TYPE_TENANT).checkLogin();
                    //}).stop();
                    // 登录校验 -- 拦截所有路由
                    SaRouter.match("/**", r -> {
                        //TODO 增加外部系统与内部系统用户打通 ？ 后续优化
                        String innerToken = SaHolder.getRequest().getHeader("ENCRYPTION-KEY");
                        if(StrUtil.isNotEmpty(innerToken)){
                            String decryptStr = SecureUtil.rsa(privateKey, null).decryptStr(innerToken, KeyType.PrivateKey);
                            String[] split = decryptStr.split("-");
                            log.info("解密后字符，{},split[0]:{}",decryptStr,split[0]);
                            log.info("current user，{}", split[1]);
                            if(openList.contains(split[0])){
                                String tokenValue = StpUtil.getTokenValueByLoginId(split[1]);
                                if(Objects.isNull(tokenValue)){
                                    UserInfo userInfo = new UserInfo();
                                    userInfo.setUserId(split[1]);
                                    userInfo.setGrantType("password");
                                    userInfo.setUserDetailKey(USERDETAIL_USER_ID);
                                    BaseSystemInfo sysconfig = getSysconfig(userInfo);
                                    userInfo = getLoginService().userInfo(userInfo, sysconfig);
                                    UserProvider.login(userInfo, this.getLoginModel(userInfo, sysconfig));
                                }else {
                                    StpUtil.setTokenValue(tokenValue);
                                }
                            }
                        }
                        StpUtil.checkLogin();
                    }).stop();
                }).setError(e -> {
                    SaHolder.getResponse().addHeader("Content-Type","application/json; charset=utf-8");
                    if(e instanceof NotLoginException){
                        return SaResult.error(ActionResultCode.SessionOverdue.getMessage()).setCode(ActionResultCode.SessionOverdue.getCode());
                    }else if(e instanceof LoginException){
                        return SaResult.error(ActionResultCode.SessionOverdue.getMessage()).setCode(ActionResultCode.SessionOverdue.getCode());
                    }
                    log.error(e.getMessage(), e);
                    return SaResult.error(MsgCode.AD102.get()).setCode(ActionResultCode.Exception.getCode());
                })
                // 前置函数：在每次认证函数之前执行
                .setBeforeAuth(obj -> {
                    HttpServletRequest request = (HttpServletRequest) SaHolder.getRequest().getSource();
                    // ---------- 设置跨域响应头 ----------
                    SaHolder.getResponse()
                            // 允许指定域访问跨域资源
                            .setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, request.getHeader(HttpHeaders.ORIGIN))
                            // 允许的header参数
                            .setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, ALL)
                            // 允许所有请求方式
                            .setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, ALL)
                            .setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true")
                            .setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL)
                            // 有效时间
                            .setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);

                    // 如果是预检请求，则立即返回到前端
                    SaRouter.match(SaHttpMethod.OPTIONS)
                            .back();
                });
    }

    public static void main(String[] args) {
        RSA rsa = SecureUtil.rsa(null,"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDFJpt6EVTpJmipq7dGS60aI313pgnt2uC0avLNxEI8vjhgQ7CCdYBneg4zhFImlfUINfJme+gZYSJGKnKJ2AkMdmqrJ93/hzUxUY84MaZ1L7rpZhkaPIQvIvmn6pD0IpMcYJMc13n1WV+rzrR3uOplnVJ4iVgy8HM4aX/asu4eRwIDAQAB");
//        System.out.println(rsa.getPrivateKeyBase64());
//        System.out.println(rsa.getPublicKeyBase64());
        System.out.println(rsa.encryptBase64("yecai-349057407209541", KeyType.PublicKey));
    }

    private BaseSystemInfo getSysconfig(UserInfo userInfo) throws LoginException {
        BaseSystemInfo baseSystemInfo = this.getLoginService().getBaseSystemConfig(userInfo.getTenantId());
        if (baseSystemInfo != null && baseSystemInfo.getSingleLogin() != null) {
            TenantProvider.setBaseSystemInfo(baseSystemInfo);
            return baseSystemInfo;
        } else {
            throw new TenantDatabaseException();
        }
    }
    private SaLoginModel getLoginModel(UserInfo userInfo, BaseSystemInfo baseSystemInfo) {
        SaLoginModel loginModel = new SaLoginModel();
        loginModel.setTimeout((long)userInfo.getTokenTimeout() * 60L);
        Map<String, Object> tokenInfo = new HashMap();
        tokenInfo.put("singleLogin", baseSystemInfo == null ? null : baseSystemInfo.getSingleLogin());
        tokenInfo.put("user_name", userInfo.getUserAccount());
        tokenInfo.put("user_id", userInfo.getUserId());
        tokenInfo.put("exp", userInfo.getOverdueTime().getTime());
        tokenInfo.put("token", userInfo.getId());
        loginModel.setExtraData(tokenInfo);
        if (userInfo.getLoginDevice() == null) {
            loginModel.setDevice("pc");
            userInfo.setLoginDevice(loginModel.device);
        } else {
            loginModel.setDevice(userInfo.getLoginDevice());
        }

        return loginModel;
    }

    public static String getIpAddr() {
        return IpUtil.getIpAddr();
    }


}
